﻿using System;
using System.Threading;

namespace NsbExtensions.Utilities
{
    public abstract class TimerServiceBase : BackgroundServiceBase
    {
        private Timer _timer;
        private TimeSpan _timerInterval;
        private long _waiting;

        protected TimerServiceBase(TimeSpan timerInterval)
        {
            Interval = timerInterval;
        }

        protected TimerServiceBase() : this(TimeSpan.FromSeconds(1))
        {
        }

        /// <summary>
        /// Setting this property to true will cause the timer service to only process one interval at a time. 
        /// If the work to be done ever happens to be slower than the interval, instead of invoking concurrent
        /// ProcessTimerAction calls, the service will wait until the first ProcessTimerAction completes to call ProcessTimer
        /// action again, essentially ensuring that two threads will never execute ProcessTimerAction at exactly the same time.
        /// </summary>
        public bool PreventConcurrentIntervalProcessing { get; set; }

        public TimeSpan Interval
        {
            get { return _timerInterval; }
            set
            {
                if (_timerInterval != value)
                {
                    _timerInterval = value;
                    if (IsRunning)
                        _timer.Change(Interval, Interval);
                }
            }
        }

        public event EventHandler TimerInterval;

        protected override void StartService()
        {
            _timer = new Timer(OnInterval, null, Interval, Interval);
        }

        protected override void ShutDownService()
        {
            _timer.Dispose();
        }


        protected virtual void OnInterval(object state)
        {
            try
            {
                if (!IsRunning)
                    return;

                //If we're serializing interval processing, we will ignore 
                //interval requests that come in while processing
                //is taking place, except to note that it happened,
                //so we can process immediately after our business is complete.
                if (PreventConcurrentIntervalProcessing)
                {
                    if (Interlocked.Read(ref _waiting) > 0)
                        return;

                    if (IsBusy)
                    {
                        Interlocked.Increment(ref _waiting);
                        return;
                    }
                }

                using (new BusyHandle(this))
                {
                    PerformTimerAction();

                    EventHandler evt = TimerInterval;
                    if (evt != null)
                        evt(this, new EventArgs());
                }

                //If we're serialized interval processing an an interval
                //occurred while we were working, we need to call PerformAction
                //and perform the delayed activity.
                if (PreventConcurrentIntervalProcessing)
                {
                    if (Interlocked.Read(ref _waiting) > 0)
                    {
                        Interlocked.Exchange(ref _waiting, 0);
                        PerformTimerAction();
                    }
                }
            }
            catch (Exception ex)
            {
                ex = new Exception("An unhandled service error occurred:" + ex.Message, ex);
                OnServiceException(ex);
            }
        }

        protected abstract void PerformTimerAction();
    }
}